package com.github.abdularis.buttonprogress;

import com.github.abdularis.buttonprogress.utils.AttrUtils;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.ElementScatter;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.*;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by abdularis on 02/01/18.
 *
 * This view class shows 4 different view in different state
 * 1. Idle state show a button (download button in this case, but not limited)
 * 2. Indeterminate state show indeterminate circular progress, with optional button in the center
 * 3. Determinate state show determinate progress, with optional button in the center
 * 4. Finish state show finish drawable or hide this view
 *
 * you can use this view to make a download or upload button, you might also use this for another
 * purpose.
 */
public class DownloadButtonProgress extends Component implements Component.ClickedListener, Component.DrawTask{
    public static final int STATE_IDLE = 1;
    public static final int STATE_INDETERMINATE = 2;
    public static final int STATE_DETERMINATE = 3;
    public static final int STATE_FINISHED = 4;

    private static final int BASE_START_ANGLE = -90;
    private static final int DEF_BG_COLOR = 0xB4000000;
    private static final boolean DEF_CANCELABLE = true;
    private static final int DEF_DETERMINATE_COLOR = Color.GREEN.getValue();
    private static final int DEF_INDETERMINATE_COLOR = Color.WHITE.getValue();
    private static final int DEF_PROGRESS_WIDTH = 8;
    private static final int DEF_PROGRESS_MARGIN = 5;
    private static final int DEF_PROGRESS_INDETERMINATE_WIDTH = 90;

    // 此值是为了解决一个边界会被裁掉一点的问题，经测试这是系统绘制问题
    private static final int OFFSET = 1;

    private Element mIdleIcon;
    private Element mCancelIcon;
    private Element mFinishIcon;

    private boolean mCancelable = DEF_CANCELABLE;

    private int mIdleIconWidth;
    private int mIdleIconHeight;
    private int mCancelIconWidth;
    private int mCancelIconHeight;
    private int mFinishIconWidth;
    private int mFinishIconHeight;

    private int mCurrState = STATE_IDLE;
    private int mMaxProgress = 100;
    private int mCurrProgress = 0;

    private int mIdleBgColor = DEF_BG_COLOR;
    private int mFinishBgColor = DEF_BG_COLOR;
    private int mIndeterminateBgColor = DEF_BG_COLOR;
    private int mDeterminateBgColor = DEF_BG_COLOR;

    private Element mIdleBgDrawable;
    private Element mFinishBgDrawable;
    private Element mIndeterminateBgDrawable;
    private Element mDeterminateBgDrawable;

    private AnimatorValue mIndeterminateAnimator;
    private int mCurrIndeterminateBarPos;
    private int mProgressIndeterminateSweepAngle = DEF_PROGRESS_INDETERMINATE_WIDTH;

    private int mProgressDeterminateColor = DEF_DETERMINATE_COLOR;
    private int mProgressIndeterminateColor = DEF_INDETERMINATE_COLOR;
    private int mProgressMargin = DEF_PROGRESS_MARGIN;

    private final Paint mBgPaint = new Paint();
    private final RectFloat mBgRect = new RectFloat();

    private final Paint mProgressPaint = new Paint();
    private final RectFloat mProgressRect = new RectFloat();

    private final List<OnClickListener> mClickListeners = new ArrayList<>();
    private final List<OnStateChangedListener> mOnStateChangedListeners = new ArrayList<>();

    public DownloadButtonProgress(Context context) {
        super(context);
        initWithContext(context, null);
    }

    public DownloadButtonProgress(Context context, AttrSet attrSet) {
        super(context, attrSet);
        initWithContext(context, attrSet);
    }

    public DownloadButtonProgress(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        initWithContext(context, attrSet);
    }

    public DownloadButtonProgress(Context context, AttrSet attrSet, int resId) {
        super(context, attrSet, resId);
        initWithContext(context, attrSet);
    }

    private void initWithContext(Context context, AttrSet attrSet){
        super.setClickedListener(this);
        addDrawTask(this);
        initIndeterminateAnimator();

        int mDefaultSize = vpToPx(context, 24);

        mProgressPaint.setStyle(Paint.Style.STROKE_STYLE);
        mProgressPaint.setDither(true);
        mProgressPaint.setAntiAlias(true);
        mProgressPaint.setStrokeJoin(Paint.Join.ROUND_JOIN);
        mProgressPaint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);
        mProgressPaint.setPathEffect(new PathEffect(50f));

        mBgPaint.setDither(true);
        mBgPaint.setAntiAlias(true);

        if(attrSet != null) {
            initBackgroundDrawableFromAttrs(context, attrSet);

            String state = AttrUtils.getStringValueByAttr(attrSet, "state", "IDLE");
            switch (state) {
                case "IDLE":
                    mCurrState = 1;
                    break;
                case "INDETERMINATE":
                    mCurrState = 2;
                    break;
                case "DETERMINATE":
                    mCurrState = 3;
                    break;
                case "FINISHED":
                    mCurrState = 4;
                    break;
                default:
                    break;
            }

            mCancelable = AttrUtils.getBooleanValueByAttr(attrSet, "cancelable", DEF_CANCELABLE);
            mProgressIndeterminateSweepAngle = AttrUtils.getIntValueByAttr(attrSet, "progressIndeterminateSweepAngle", DEF_PROGRESS_INDETERMINATE_WIDTH);
            mProgressDeterminateColor = AttrUtils.getColorValueByAttr(attrSet, "progressDeterminateColor", new Color(DEF_DETERMINATE_COLOR)).getValue();
            mProgressIndeterminateColor = AttrUtils.getColorValueByAttr(attrSet, "progressIndeterminateColor", new Color(DEF_INDETERMINATE_COLOR)).getValue();
            mProgressPaint.setStrokeWidth(AttrUtils.getFloatValueByAttr(attrSet, "progressWidth", DEF_PROGRESS_WIDTH));
            mProgressMargin = AttrUtils.getIntValueByAttr(attrSet, "progressMargin", DEF_PROGRESS_MARGIN);
            mCurrProgress = AttrUtils.getIntValueByAttr(attrSet, "currentProgress", 0);
            mMaxProgress = AttrUtils.getIntValueByAttr(attrSet, "maxProgress", 100);

            mIdleIcon = AttrUtils.getElementValueByAttr(attrSet, "idleIconDrawable", ElementScatter.getInstance(context).parse(ResourceTable.Graphic_ic_default_download));
            mIdleIconWidth = AttrUtils.getDimensionValueByAttr(attrSet, "idleIconWidth", mIdleIcon.getMinWidth());
            mIdleIconHeight = AttrUtils.getDimensionValueByAttr(attrSet, "idleIconHeight", mIdleIcon.getMinHeight());

            mCancelIcon = AttrUtils.getElementValueByAttr(attrSet, "cancelIconDrawable", ElementScatter.getInstance(context).parse(ResourceTable.Graphic_ic_default_cancel));
            mCancelIconWidth = AttrUtils.getDimensionValueByAttr(attrSet, "cancelIconWidth", mCancelIcon.getMinWidth());
            mCancelIconHeight = AttrUtils.getDimensionValueByAttr(attrSet, "cancelIconHeight", mCancelIcon.getMinHeight());

            mFinishIcon = AttrUtils.getElementValueByAttr(attrSet, "finishIconDrawable", ElementScatter.getInstance(context).parse(ResourceTable.Graphic_ic_default_finish));
            mFinishIconWidth = AttrUtils.getDimensionValueByAttr(attrSet, "finishIconWidth", mFinishIcon.getMinWidth());
            mFinishIconHeight = AttrUtils.getDimensionValueByAttr(attrSet, "finishIconHeight", mFinishIcon.getMinHeight());
        }else {
            mProgressPaint.setStrokeWidth(DEF_PROGRESS_WIDTH);

            mIdleIcon = ElementScatter.getInstance(context).parse(ResourceTable.Graphic_ic_default_download);
            mIdleIconWidth = mIdleIcon.getMinWidth();
            mIdleIconHeight = mIdleIcon.getMinHeight();

            mCancelIcon = ElementScatter.getInstance(context).parse(ResourceTable.Graphic_ic_default_cancel);
            mCancelIconWidth = mCancelIcon.getMinWidth();
            mCancelIconHeight = mCancelIcon.getMinHeight();

            mFinishIcon = ElementScatter.getInstance(context).parse(ResourceTable.Graphic_ic_default_finish);
            mFinishIconWidth = mFinishIcon.getMinWidth();
            mFinishIconHeight = mFinishIcon.getMinHeight();
        }

        if(mIdleIconWidth == 0 || mIdleIconHeight == 0){
            mIdleIconWidth = mDefaultSize;
            mIdleIconHeight = mDefaultSize;
        }
        if(mCancelIconWidth == 0 || mCancelIconHeight == 0){
            mCancelIconWidth = mDefaultSize;
            mCancelIconHeight = mDefaultSize;
        }
        if(mFinishIconWidth == 0 || mFinishIconHeight == 0){
            mFinishIconWidth = mDefaultSize;
            mFinishIconHeight = mDefaultSize;
        }

        if (mCurrState == STATE_INDETERMINATE) {
            setIndeterminate();
        }
    }

    private void initBackgroundDrawableFromAttrs(Context context, AttrSet attrSet) {
        if(attrSet.getAttr("idleBackgroundDrawable").isPresent()){
            mIdleBgDrawable = AttrUtils.getElementValueByAttr(attrSet, "idleBackgroundDrawable", new ShapeElement());
        }
        if(attrSet.getAttr("finishBackgroundDrawable").isPresent()){
            mFinishBgDrawable = AttrUtils.getElementValueByAttr(attrSet, "finishBackgroundDrawable", new ShapeElement());
        }
        if(attrSet.getAttr("indeterminateBackgroundDrawable").isPresent()){
            mIndeterminateBgDrawable = AttrUtils.getElementValueByAttr(attrSet, "indeterminateBackgroundDrawable", new ShapeElement());
        }
        if(attrSet.getAttr("determinateBackgroundDrawable").isPresent()){
            mDeterminateBgDrawable = AttrUtils.getElementValueByAttr(attrSet, "determinateBackgroundDrawable", new ShapeElement());
        }

        if(attrSet.getAttr("idleBackgroundColor").isPresent()){
            mIdleBgColor = AttrUtils.getColorValueByAttr(attrSet, "idleBackgroundColor", new Color(DEF_BG_COLOR)).getValue();
        }
        if(attrSet.getAttr("finishBackgroundColor").isPresent()){
            mFinishBgColor = AttrUtils.getColorValueByAttr(attrSet, "finishBackgroundColor", new Color(DEF_BG_COLOR)).getValue();
        }
        if(attrSet.getAttr("indeterminateBackgroundColor").isPresent()){
            mIndeterminateBgColor = AttrUtils.getColorValueByAttr(attrSet, "indeterminateBackgroundColor", new Color(DEF_BG_COLOR)).getValue();
        }
        if(attrSet.getAttr("determinateBackgroundColor").isPresent()){
            mDeterminateBgColor = AttrUtils.getColorValueByAttr(attrSet, "determinateBackgroundColor", new Color(DEF_BG_COLOR)).getValue();
        }
    }

    public int getCurrState() {
        return mCurrState;
    }

    public int getMaxProgress() {
        return mMaxProgress;
    }

    public int getCurrentProgress() {
        return mCurrProgress;
    }

    public Element getIdleIcon() {
        return mIdleIcon;
    }

    public Element getCancelIcon() {
        return mCancelIcon;
    }

    public Element getFinishIcon() {
        return mFinishIcon;
    }

    public boolean isCancelable() {
        return mCancelable;
    }

    public int getIdleIconWidth() {
        return mIdleIconWidth;
    }

    public int getIdleIconHeight() {
        return mIdleIconHeight;
    }

    public int getCancelIconWidth() {
        return mCancelIconWidth;
    }

    public int getCancelIconHeight() {
        return mCancelIconHeight;
    }

    public int getFinishIconWidth() {
        return mFinishIconWidth;
    }

    public int getFinishIconHeight() {
        return mFinishIconHeight;
    }

    public int getIdleBgColor() {
        return mIdleBgColor;
    }

    public int getFinishBgColor() {
        return mFinishBgColor;
    }

    public int getIndeterminateBgColor() {
        return mIndeterminateBgColor;
    }

    public int getDeterminateBgColor() {
        return mDeterminateBgColor;
    }

    public Element getIdleBgDrawable() {
        return mIdleBgDrawable;
    }

    public Element getFinishBgDrawable() {
        return mFinishBgDrawable;
    }

    public Element getIndeterminateBgDrawable() {
        return mIndeterminateBgDrawable;
    }

    public Element getDeterminateBgDrawable() {
        return mDeterminateBgDrawable;
    }

    public int getProgressDeterminateColor() {
        return mProgressDeterminateColor;
    }

    public int getProgressIndeterminateColor() {
        return mProgressIndeterminateColor;
    }

    public int getProgressMargin() {
        return mProgressMargin;
    }

    public int getProgressIndeterminateSweepAngle() {
        return mProgressIndeterminateSweepAngle;
    }

    public void setCurrentProgress(int progress) {
        if (mCurrState != STATE_DETERMINATE) return;
        mCurrProgress = Math.min(progress, mMaxProgress);
        invalidate();
    }

    public void setIdle() {
        mCurrState = STATE_IDLE;
        callStateChangedListener(mCurrState);

        invalidate();
    }

    public void setIndeterminate() {
        mCurrIndeterminateBarPos = BASE_START_ANGLE;
        mCurrState = STATE_INDETERMINATE;
        callStateChangedListener(mCurrState);

        invalidate();

        mIndeterminateAnimator.start();
    }

    public void setDeterminate() {
        mIndeterminateAnimator.end();

        mCurrProgress = 0;
        mCurrState = STATE_DETERMINATE;
        callStateChangedListener(mCurrState);

        invalidate();
    }

    public void setFinish() {
        mCurrProgress = 0;
        mCurrState = STATE_FINISHED;
        callStateChangedListener(mCurrState);

        invalidate();
    }

    public void setIdleIcon(ShapeElement idleIcon) {
        mIdleIcon = idleIcon;
        invalidate();
    }

    public void setCancelIcon(ShapeElement cancelIcon) {
        mCancelIcon = cancelIcon;
        invalidate();
    }

    public void setFinishIcon(ShapeElement finishIcon) {
        mFinishIcon = finishIcon;
        invalidate();
    }

    public void setCancelable(boolean cancelable) {
        mCancelable = cancelable;
        invalidate();
    }

    public void setIdleIconWidth(int idleIconWidth) {
        mIdleIconWidth = idleIconWidth;
        invalidate();
    }

    public void setIdleIconHeight(int idleIconHeight) {
        mIdleIconHeight = idleIconHeight;
        invalidate();
    }

    public void setCancelIconWidth(int cancelIconWidth) {
        mCancelIconWidth = cancelIconWidth;
        invalidate();
    }

    public void setCancelIconHeight(int cancelIconHeight) {
        mCancelIconHeight = cancelIconHeight;
        invalidate();
    }

    public void setFinishIconWidth(int finishIconWidth) {
        mFinishIconWidth = finishIconWidth;
        invalidate();
    }

    public void setFinishIconHeight(int finishIconHeight) {
        mFinishIconHeight = finishIconHeight;
        invalidate();
    }

    public void setMaxProgress(int maxProgress) {
        mMaxProgress = maxProgress;
        invalidate();
    }

    public void setIdleBgColor(int idleBgColor) {
        mIdleBgColor = idleBgColor;
        invalidate();
    }

    public void setFinishBgColor(int finishBgColor) {
        mFinishBgColor = finishBgColor;
        invalidate();
    }

    public void setIndeterminateBgColor(int indeterminateBgColor) {
        mIndeterminateBgColor = indeterminateBgColor;
        invalidate();
    }

    public void setDeterminateBgColor(int determinateBgColor) {
        mDeterminateBgColor = determinateBgColor;
        invalidate();
    }

    public void setIdleBgDrawable(ShapeElement idleBgDrawable) {
        mIdleBgDrawable = idleBgDrawable;
        invalidate();
    }

    public void setFinishBgDrawable(ShapeElement finishBgDrawable) {
        mFinishBgDrawable = finishBgDrawable;
        invalidate();
    }

    public void setIndeterminateBgDrawable(ShapeElement indeterminateBgDrawable) {
        mIndeterminateBgDrawable = indeterminateBgDrawable;
        invalidate();
    }

    public void setDeterminateBgDrawable(ShapeElement determinateBgDrawable) {
        mDeterminateBgDrawable = determinateBgDrawable;
        invalidate();
    }

    public void setProgressDeterminateColor(int progressDeterminateColor) {
        mProgressDeterminateColor = progressDeterminateColor;
        invalidate();
    }

    public void setProgressIndeterminateColor(int progressIndeterminateColor) {
        mProgressIndeterminateColor = progressIndeterminateColor;
        invalidate();
    }

    public void setProgressMargin(int progressMargin) {
        mProgressMargin = progressMargin;
        invalidate();
    }

    public void setProgressIndeterminateSweepAngle(int progressIndeterminateSweepAngle) {
        mProgressIndeterminateSweepAngle = progressIndeterminateSweepAngle;
        invalidate();
    }

    public void addOnClickListener(OnClickListener listener) {
        if (!mClickListeners.contains(listener))
            mClickListeners.add(listener);
    }

    public void removeOnClickListener(OnClickListener listener) {
        mClickListeners.remove(listener);
    }

    public void addOnStateChangedListeners(OnStateChangedListener listener) {
        if (!mOnStateChangedListeners.contains(listener))
            mOnStateChangedListeners.add(listener);
    }

    public void removeOnStateChangedListener(OnStateChangedListener listener) {
        mOnStateChangedListeners.remove(listener);
    }

    private void callStateChangedListener(int newState) {
        for (OnStateChangedListener listener : mOnStateChangedListeners)
            listener.onStateChanged(newState);
    }

    @Override
    public void onClick(Component v) {
        if (!mCancelable && (mCurrState == STATE_INDETERMINATE || mCurrState == STATE_DETERMINATE))
            return;

        if (mCurrState == STATE_IDLE) {
            for (OnClickListener listener : mClickListeners)
                listener.onIdleButtonClick(v);
        }
        else if (mCurrState == STATE_INDETERMINATE || mCurrState == STATE_DETERMINATE) {
            for (OnClickListener listener : mClickListeners)
                listener.onCancelButtonClick(v);
        }
        else if (mCurrState == STATE_FINISHED) {
            for (OnClickListener listener : mClickListeners)
                listener.onFinishButtonClick(v);
        }
    }

    private void drawIdleState(Canvas canvas) {
        if (mIdleBgDrawable != null) {
            mIdleBgDrawable.setBounds(OFFSET, OFFSET, getWidth() - OFFSET, getHeight() - OFFSET);
            mIdleBgDrawable.drawToCanvas(canvas);
        } else {
            mBgRect.fuse(0, 0, getWidth(), getHeight());
            mBgPaint.setColor(new Color(mIdleBgColor));
            canvas.drawOval(mBgRect, mBgPaint);
        }

        drawDrawableInCenter(mIdleIcon, canvas, mIdleIconWidth, mIdleIconHeight);
    }

    private void drawFinishState(Canvas canvas) {
        if (mFinishBgDrawable != null) {
            mFinishBgDrawable.setBounds(OFFSET, OFFSET, getWidth() - OFFSET, getHeight() - OFFSET);
            mFinishBgDrawable.drawToCanvas(canvas);
        } else {
            mBgRect.fuse(0, 0, getWidth(), getHeight());
            mBgPaint.setColor(new Color(mFinishBgColor));
            canvas.drawOval(mBgRect, mBgPaint);
        }

        drawDrawableInCenter(mFinishIcon, canvas, mFinishIconWidth, mFinishIconHeight);
    }

    private void drawIndeterminateState(Canvas canvas) {
        if (mIndeterminateBgDrawable != null) {
            mIndeterminateBgDrawable.setBounds(OFFSET, OFFSET, getWidth() - OFFSET, getHeight() - OFFSET);
            mIndeterminateBgDrawable.drawToCanvas(canvas);
        } else {
            mBgRect.fuse(0, 0, getWidth(), getHeight());
            mBgPaint.setColor(new Color(mIndeterminateBgColor));
            canvas.drawOval(mBgRect, mBgPaint);
        }

        if (mCancelable) {
            drawDrawableInCenter(mCancelIcon, canvas, mCancelIconWidth, mCancelIconHeight);
        }

        setProgressRectBounds();
        mProgressPaint.setColor(new Color(mProgressIndeterminateColor));
        Arc arc = new Arc();
        arc.setArc(mCurrIndeterminateBarPos, mProgressIndeterminateSweepAngle, false);
        canvas.drawArc(mProgressRect, arc, mProgressPaint);
    }

    private void drawDeterminateState(Canvas canvas) {
        if (mDeterminateBgDrawable != null) {
            mDeterminateBgDrawable.setBounds(OFFSET, OFFSET, getWidth() - OFFSET, getHeight() - OFFSET);
            mDeterminateBgDrawable.drawToCanvas(canvas);
        } else {
            mBgRect.fuse(0, 0, getWidth(), getHeight());
            mBgPaint.setColor(new Color(mDeterminateBgColor));
            canvas.drawOval(mBgRect, mBgPaint);
        }

        if (mCancelable) {
            drawDrawableInCenter(mCancelIcon, canvas, mCancelIconWidth, mCancelIconHeight);
        }

        setProgressRectBounds();
        mProgressPaint.setColor(new Color(mProgressDeterminateColor));
        Arc arc = new Arc();
        arc.setArc(BASE_START_ANGLE, getDegrees(), false);
        canvas.drawArc(mProgressRect, arc, mProgressPaint);
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (mCurrState == STATE_IDLE) {
            drawIdleState(canvas);
        }
        else if (mCurrState == STATE_INDETERMINATE) {
            drawIndeterminateState(canvas);
        }
        else if (mCurrState == STATE_DETERMINATE) {
            drawDeterminateState(canvas);
        }
        else if (mCurrState == STATE_FINISHED) {
            drawFinishState(canvas);
        }
    }

    private void setProgressRectBounds() {
        float halfStroke = mProgressPaint.getStrokeWidth() / 2.0f;
        float totalMargin = mProgressMargin + halfStroke;
        mProgressRect.fuse(totalMargin, totalMargin, getWidth() - totalMargin, getHeight() - totalMargin);
    }

    private void initIndeterminateAnimator() {
        mIndeterminateAnimator = new AnimatorValue();
        mIndeterminateAnimator.setCurveType(Animator.CurveType.LINEAR);
        mIndeterminateAnimator.setDuration(1000);
        mIndeterminateAnimator.setLoopedCount(Animator.INFINITE);
        mIndeterminateAnimator.setValueUpdateListener((animatorValue, v) -> {
            int value = (int) (v * 360);
            mCurrIndeterminateBarPos = value - BASE_START_ANGLE;
            invalidate();
        });
    }

    private float getDegrees() {
        return ((float)mCurrProgress / (float)mMaxProgress) * 360;
    }

    private void drawDrawableInCenter(Element drawable, Canvas canvas, int width, int height) {
        int left = (getWidth() / 2) - (width / 2);
        int top = (getHeight() / 2) - (height / 2);
        drawable.setBounds(left, top, left + width, top + height);
        drawable.drawToCanvas(canvas);
    }

    private int vpToPx(Context context, int vp) {
        return (int) DisplayManager.getInstance().getDefaultDisplay(context).get().getAttributes().densityPixels*vp;
    }

    public interface OnClickListener {
        void onIdleButtonClick(Component view);
        void onCancelButtonClick(Component view);
        void onFinishButtonClick(Component view);
    }

    public interface OnStateChangedListener {
        void onStateChanged(int newState);
    }
}
